package cn.demoncat.util.es.util;

import cn.demoncat.util.lang.LocalDateUtil;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.*;

import java.util.Collection;

/**
 * ElasticSearch QueryBuilder 工具
 * <pre>
 * 公共设置：
 * 
 * 1、boost：设置查询的评分因子，0 ~ 1 减分，1 + 加分
 * </pre>
 * @author 延晓磊
 *
 * @since 2020-9-5
 * 
 * @see <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-query-dsl.html">Java API</a>
 */
public class QueryBuilderUtil {

	/**
	 * 查询全部
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-2
	 */
	public static MatchAllQueryBuilder all() {
		return QueryBuilders.matchAllQuery();
	}
	
	/**
	 * 组合查询
	 * 
	 * must：AND
	 * mustNot：AND NOT
	 * should：OR
	 * filter：AND，过滤但不计分
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static BoolQueryBuilder bool() {
		return QueryBuilders.boolQuery();
	}
	
	/**
	 * 组合查询 - bool.must：AND
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static BoolQueryBuilder and(QueryBuilder...queries) {
		 BoolQueryBuilder bool = bool();
		 for (QueryBuilder query : queries) {
			if (query != null) {
				bool = bool.must(query);
			}
		}
		return bool;
	}

	/**
	 * 组合查询 - bool.must：AND
	 *
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static BoolQueryBuilder and(Collection<QueryBuilder> queries) {
		BoolQueryBuilder bool = bool();
		for (QueryBuilder query : queries) {
			if (query != null) {
				bool = bool.must(query);
			}
		}
		return bool;
	}

	/**
	 * 组合查询 - should：OR
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static BoolQueryBuilder or(QueryBuilder...queries) {
		 BoolQueryBuilder bool = bool();
		 for (QueryBuilder query : queries) {
			if (query != null) {
				bool = bool.should(query);
			}
		}
		return bool;
	}

	/**
	 * 组合查询 - should：OR
	 *
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static BoolQueryBuilder or(Collection<QueryBuilder> queries) {
		BoolQueryBuilder bool = bool();
		for (QueryBuilder query : queries) {
			if (query != null) {
				bool = bool.should(query);
			}
		}
		return bool;
	}

	/**
	 * 组合查询 - mustNot：AND NOT
	 *
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static BoolQueryBuilder not(QueryBuilder...queries) {
		BoolQueryBuilder bool = bool();
		for (QueryBuilder query : queries) {
			if (query != null) {
				bool = bool.mustNot(query);
			}
		}
		return bool;
	}

	/**
	 * 降级查询
	 * 
	 * @param positive	正查询：匹配positive的文档会被查询，词频越高、内容越短的得分越高
	 * @param negative	负查询：匹配negative的文档的得分会乘以boost，从而减少得分并排序在后
	 * @param boost		负查询计分因子：0 ~ 1
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static BoostingQueryBuilder boosting(QueryBuilder positive, QueryBuilder negative, float boost) {
		return QueryBuilders.boostingQuery(positive, negative).negativeBoost(boost);
	}
	
	/**
	 * 分词查询
	 * 
	 * @param field
	 * @param value
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static MatchQueryBuilder match(String field, String value) {
		return QueryBuilders.matchQuery(field, value);
	}
	
	/**
	 * 多字段分词查询
	 * 
	 * @param value
	 * @param field
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static MultiMatchQueryBuilder multiMatch(String value, String... field) {
		return QueryBuilders.multiMatchQuery(value, field);
	}
	
	/**
	 * 短语分词查询
	 * 
	 * @param field
	 * @param value
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static MatchPhraseQueryBuilder matchPhrase(String field, String value) {
		return QueryBuilders.matchPhraseQuery(field, value);
	}
	
	/**
	 * 短语前缀分词查询
	 * 
	 * @param value
	 * @param field
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static MatchPhrasePrefixQueryBuilder matchPhrasePrefix(String field, String value) {
		return QueryBuilders.matchPhrasePrefixQuery(field, value);
	}
	
	/**
	 * 精确查询
	 * 
	 * @param field
	 * @param value 匹配的值：=(value)，数组的多值匹配，使用多个term
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static TermQueryBuilder term(String field, Object value) {
		return QueryBuilders.termQuery(field, value);
	}
	
	/**
	 * 多值精确查询
	 * 
	 * @param field
	 * @param value	匹配的值：IN(value)
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static TermsQueryBuilder terms(String field, Object... value) {
		return QueryBuilders.termsQuery(field, value);
	}

	/**
	 * 多值精确查询
	 *
	 * @param field
	 * @param value	匹配的值：IN(value)
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static TermsQueryBuilder terms(String field, Collection<?> value) {
		return QueryBuilders.termsQuery(field, value);
	}

	/**
	 * 字段 - 大于
	 * 
	 * @param field		字段名
	 * @param value		比较值
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年8月28日
	 */
	public static RangeQueryBuilder gt(String field, Object value) {
		return QueryBuilders.rangeQuery(field).from(value, false);
	}
	
	/**
	 * 字段 - 大于等于
	 * 
	 * @param field		字段名
	 * @param value		比较值
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年8月28日
	 */
	public static RangeQueryBuilder gte(String field, Object value) {
		return QueryBuilders.rangeQuery(field).from(value, true);
	}
	
	/**
	 * 字段 - 小于
	 * 
	 * @param field		字段名
	 * @param value		比较值
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年8月28日
	 */
	public static RangeQueryBuilder lt(String field, Object value) {
		return QueryBuilders.rangeQuery(field).to(value, false);
	}
	
	/**
	 * 字段 - 小于等于
	 * 
	 * @param field		字段名
	 * @param value		比较值
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年8月28日
	 */
	public static RangeQueryBuilder lte(String field, Object value) {
		return QueryBuilders.rangeQuery(field).to(value, true);
	}
	
	/**
	 * 区间查询
	 * 
	 * @param field
	 * @param from	最小值，包含
	 * @param to	最大值，不包含
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static RangeQueryBuilder between(String field, Object from, Object to) {
		if (from == null && to == null) {
			return null;
		}
		RangeQueryBuilder query = QueryBuilders.rangeQuery(field);
		if (from != null) {
			query.from(from,true);
		}
		if (to != null) {
			query.to(to, false);
		}
		return query;
	}
	
	/**
	 * 时间区间
	 * 
	 * @param field		字段名
	 * @param startTime	起始时间
	 * @param endTime	结束时间
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static QueryBuilder timeRange(String field, String startTime, String endTime) {
		return between(field, LocalDateUtil.toLocalDateTime(startTime), LocalDateUtil.toLocalDateTime(endTime));
	}
	
	/**
	 * 非空查询
	 * 
	 * @param field	查询字段：排除null/[]
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static ExistsQueryBuilder exists(String field) {
		return QueryBuilders.existsQuery(field);
	}
	
	/**
	 * 前缀查询
	 * 
	 * @param field
	 * @param value	前缀
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static PrefixQueryBuilder prefix(String field, String value) {
		return QueryBuilders.prefixQuery(field, value);
	}
	
	/**
	 * 通配符查询
	 * 
	 * @param field
	 * @param value	通配符：? 单个字符，* 多个字段
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static WildcardQueryBuilder wildcard(String field, String value) {
		return QueryBuilders.wildcardQuery(field, value);
	}
	
	/**
	 * 正则查询
	 * 
	 * @param field
	 * @param value	正则：^a.*b$
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static RegexpQueryBuilder regexp(String field, String value) {
		return QueryBuilders.regexpQuery(field, value);
	}
	
	/**
	 * 模糊查询
	 * 
	 * @param field
	 * @param value	查询值，允许有少量不同（查询相似的单词）
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static FuzzyQueryBuilder fuzzy(String field, Object value) {
		return QueryBuilders.fuzzyQuery(field, value);
	}
	
	/**
	 * ID集查询
	 * 
	 * @param ids
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 */
	public static IdsQueryBuilder ids(String...ids) {
		return QueryBuilders.idsQuery().addIds(ids);
	}
	
	/**
	 * 地理空间 - 方格区域
	 * 
	 * @param field			位置字段：GeoPoint[x,y]
	 * @param leftTopX	左上角
	 * @param leftTopY
	 * @param rightBottomX	右下角
	 * @param rightBottomY
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年8月28日
	 */
	public static GeoBoundingBoxQueryBuilder withinBox(String field, double leftTopX, double leftTopY, double rightBottomX, double rightBottomY) {
		return QueryBuilders.geoBoundingBoxQuery(field).setCorners(new GeoPoint(leftTopX, leftTopY), new GeoPoint(rightBottomX, rightBottomY));
	}
	
	/**
	 * 地理空间 - 附近
	 * 
	 * @param field		位置字段：double[x,y]
	 * @param x			基准位置
	 * @param y
	 * @param distance	最远距离
	 * @param unit		距离单位
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年8月28日
	 */
	public static GeoDistanceQueryBuilder near(String field, double x, double y, double distance, DistanceUnit unit) {
		return QueryBuilders.geoDistanceQuery(field).point(new GeoPoint(x, y)).distance(distance, unit);
	}

	/**
	 * 嵌套查询（对象数组的扁平化查询）
	 *
	 * <pre>
	    # mapping
	 	"user": {
	   		"type": "nested",
	 		"properties": {"first":"keyword","last":"keyword"}
	    }
	 	# insert
		 {
		 	"user" : [
				 {
				 	"first" : "John",
				 	"last" :  "Smith"
				 },
				 {
				 	"first" : "Alice",
				 	"last" :  "White"
				 }
			 ]
		 }
	 	# query
		 {
		 	"query": {
		 		"nested": {
		 			"path": "user",
		 			"query": {
		 				"bool": {
		 					"must": [
		 						{ "match": { "user.first": "John" } },
								{ "match": { "user.last":  "Smith" }}
	 						]
		 				}
		 			},
		 			"score_mode": "avg"
		 		}
		 	}
		 }
	 * </pre>
	 * @param field		嵌套对象名
	 * @param queryBuilder	查询条件：查询字段为 field.字段
	 * @param scoreMode		评分模式：Avg平均所有子对象，Max选择最大的子对象，None不计评分
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2020年8月28日
	 */
	public static NestedQueryBuilder nested(String field, QueryBuilder queryBuilder, ScoreMode scoreMode){
		return QueryBuilders.nestedQuery(field, queryBuilder, scoreMode);
	}

	/**
	 * 嵌套查询
	 *
	 * @param field		嵌套对象名
	 * @param queryBuilder	查询条件：查询字段为 field.字段
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2020年8月28日
	 */
	public static NestedQueryBuilder nested(String field, QueryBuilder queryBuilder){
		return QueryBuilders.nestedQuery(field, queryBuilder, ScoreMode.Max);
	}
}
